/*
 *  Copyright 2012 Alexey Andreev.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package ru.justplay.core;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.nop.actors.ActorInfo;
import org.nop.actors.ActorManager;
import org.nop.core.BeanScope;
import org.nop.core.ManagedBean;
import org.nop.sql.DataManager;
import org.nop.sql.DataResult;
import org.nop.sql.QueryBuilder;
import org.nop.util.Injected;
import ru.justplay.core.api.GameInformation;
import ru.justplay.core.api.GameManager;
import ru.justplay.core.api.PlayerInformation;
import ru.justplay.core.api.SessionInformation;
import ru.justplay.core.data.GameSource;
import ru.justplay.core.data.GameTokenSource;
import ru.justplay.core.data.InvitationSource;

/**
 *
 * @author Alexey Andreev
 */
@ManagedBean(iface = GameManager.class, scope = BeanScope.REQUEST)
public class GameManagerImpl implements GameManager {
    private static final char[] tokenChars = "abcdefghijklmnopqrstuvwxyz0123456789".toCharArray();
    private DataManager dmgr;
    private QueryBuilder qb;
    private ActorManager actorManager;
    private GameFeatureImpl gameFeature;

    @Injected
    public GameManagerImpl(DataManager dmgr, ActorManager actorManager,
            GameFeatureImpl gameFeature) {
        this.dmgr = dmgr;
        this.qb = dmgr.getQueryBuilder();
        this.actorManager = actorManager;
        this.gameFeature = gameFeature;
    }

    @Override
    public PlayerInformation useInvitation(String invitationToken) {
        InvitationSource inv = qb.get(InvitationSource.class);
        GameTokenSource gameToken = qb.get(GameTokenSource.class);
        DataResult dr = dmgr.exec(qb.with(inv)
                .join(gameToken, gameToken.gameId().eq(inv.gameId())
                        .and(gameToken.playerNumber().eq(inv.playerNumber())))
                .filter(inv.token().eq(invitationToken))
                .filter(inv.used().isFalse())
                .fetch(inv.playerNumber(), inv.gameId(), gameToken.token()));
        if (!dr.next()) {
            return null;
        }
        PlayerInformation result = new PlayerInformation();
        result.setGameId(dr.getInt(2));
        result.setPasswordToken(dr.getString(3));
        dr.close();
        int updateCount = dmgr.exec(qb.with(inv)
                .filter(inv.token().eq(invitationToken))
                .filter(inv.used().isFalse())
                .set(inv.used(), true));
        if (updateCount == 0) {
            return null;
        }
        return result;
    }

    @Override
    public GameInformation createGame(String gameName) {
        Game gameType = gameFeature.getGame(gameName);
        List<String> secrets = new ArrayList<String>();
        int playerCount = gameType.getPlayerCount();
        for (int i = 0; i < playerCount; ++i) {
            secrets.add(generateToken(12));
        }

        Object instance = gameType.createInstance(secrets);
        @SuppressWarnings("unchecked")
        Class<Object> safeGameIface = (Class<Object>)gameType.getGameInterface();
        ActorInfo info = actorManager.exportActor(safeGameIface, instance);
        long gameId = dmgr.nextLong(GameSource.ID_SEQUENCE);
        GameSource game = qb.get(GameSource.class);
        GameTokenSource gameToken = qb.get(GameTokenSource.class);
        dmgr.exec(qb.insertInto(game)
                .field(game.id(), gameId)
                .field(game.sessionId(), info.getSessionId())
                .field(game.nodeId(), info.getNodeId())
                .field(game.actorId(), info.getActorId())
                .field(game.type(), gameName));
        for (int i = 0; i < playerCount; ++i) {
            dmgr.exec(qb.insertInto(gameToken)
                    .field(gameToken.gameId(), gameId)
                    .field(gameToken.playerNumber(), i)
                    .field(gameToken.token(), secrets.get(i)));
        }

        List<String> invitations = new ArrayList<String>();
        for (int i = 0; i < playerCount; ++i) {
            invitations.add(generateInvitation(gameId, i));
        }
        GameInformation result = new GameInformation();
        result.setInvitations(invitations);
        return result;
    }

    private String generateInvitation(long gameId, int playerNumber) {
        InvitationSource inv = qb.get(InvitationSource.class);
        String token = null;
        for (int i = 0; i < 20; ++i) {
            token = generateToken(12);
            boolean ok = dmgr.exec(qb.insertInto(inv)
                    .field(inv.token(), token)
                    .field(inv.playerNumber(), playerNumber)
                    .field(inv.gameId(), gameId)
                    .field(inv.used(), false));
            if (ok) {
                break;
            } else {
                token = null;
            }
        }
        if (token == null) {
            throw new RuntimeException("Could not generate invitation");
        }
        return token;
    }

    private static String generateToken(int length) {
        Random random = new Random();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; ++i) {
            sb.append(tokenChars[random.nextInt(tokenChars.length)]);
        }
        return sb.toString();
    }

    @Override
    public SessionInformation getSessionInformation(long gameId) {
        GameSource game = qb.get(GameSource.class);
        DataResult dr = dmgr.exec(qb.with(game)
                .filter(game.id().eq(gameId))
                .fetch(game.sessionId(), game.nodeId(), game.actorId()));
        if (!dr.next()) {
            return null;
        }
        SessionInformation result = new SessionInformation();
        result.setSessionId(dr.getString(1));
        result.setNodeId(dr.getInt(2));
        result.setActorId(dr.getInt(3));
        return result;
    }
}
